#include <cstdio>
#include <pthread.h>
#include <cstdint>
#include "uvthread.h"

namespace uv {
namespace thread {

size_t RecurrentThread::thread_count = 0;

RecurrentThread::RecurrentThread() : flags(0) {
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    int rc = pthread_create(&thread, &attr, RecurrentThread::mainLoopHelper,
            this);
    pthread_attr_destroy(&attr);

    if (rc) {
        // Pthread error occurred.
    }
    tid = thread_count++;
    pthread_mutex_init(&flag_mutex, NULL);
    pthread_cond_init(&flag_cv, NULL);
}

RecurrentThread::~RecurrentThread() {
    pthread_mutex_destroy(&flag_mutex);
    pthread_cond_destroy(&flag_cv);
    pthread_exit(NULL);
}

void *RecurrentThread::mainLoopHelper(void *context) {
    return ((RecurrentThread *) context)->mainLoop();
}

void *RecurrentThread::mainLoop() {
    while (true) {
        pthread_mutex_lock(&flag_mutex);

        // Wait for a tick or stop command.
        while (!flags) {
            pthread_cond_wait(&flag_cv, &flag_mutex);
        }

        // Ok, lets see what we need to do.
        if (flags & (TICK_FLAG | ONE_TICK_FLAG)) {
            // Tick.
            task(task_args.front());
            task_args.pop_front();
            if (task_args.empty()) {
                flags &= ~(TICK_FLAG | ONE_TICK_FLAG);
            }
        } else if (flags & STOP_FLAG) {
            // Stop running.
            flags |= DONE_FLAG;
            pthread_cond_signal(&flag_cv);
            pthread_mutex_unlock(&flag_mutex);
            break;
        }
        pthread_cond_signal(&flag_cv);
        pthread_mutex_unlock(&flag_mutex);
    }
    return 0;
}

bool RecurrentThread::tick(void *arg) {
    bool ret = true;
    pthread_mutex_lock(&flag_mutex);

    if (flags & ONE_TICK_FLAG) {
        ret = false;
    } else {
        flags |= TICK_FLAG;
        task_args.push_back(arg);
    }

    pthread_cond_signal(&flag_cv);
    pthread_mutex_unlock(&flag_mutex);

    return ret;
}

bool RecurrentThread::tickBlocking(void *arg) {
    bool ret = true;
    pthread_mutex_lock(&flag_mutex);

    if (flags & (ONE_TICK_FLAG | TICK_FLAG)) {
        ret = false;
    } else {
        flags |= ONE_TICK_FLAG;
        task_args.push_back(arg);
    }

    pthread_cond_signal(&flag_cv);
    pthread_mutex_unlock(&flag_mutex);

    return ret;
}

void RecurrentThread::stop() {
    pthread_mutex_lock(&flag_mutex);

    flags |= STOP_FLAG;

    pthread_cond_signal(&flag_cv);
    pthread_mutex_unlock(&flag_mutex);
}

void RecurrentThread::join() {
    stop();

    pthread_mutex_lock(&flag_mutex);

    while (flags & DONE_FLAG == 0) {
        pthread_cond_wait(&flag_cv, &flag_mutex);
    }

    pthread_cond_signal(&flag_cv);
    pthread_mutex_unlock(&flag_mutex);

    pthread_join(thread, NULL);
}

bool RecurrentThread::isRunning() {
    bool running;
    pthread_mutex_lock(&flag_mutex);

    running = (flags & DONE_FLAG) != 0;

    pthread_cond_signal(&flag_cv);
    pthread_mutex_unlock(&flag_mutex);

    return running;
}

bool RecurrentThread::isReady() {
    bool ready;
    pthread_mutex_lock(&flag_mutex);

    ready = !(flags & DONE_FLAG) && !(flags & TICK_FLAG) &&
            !(flags & ONE_TICK_FLAG);

    pthread_cond_signal(&flag_cv);
    pthread_mutex_unlock(&flag_mutex);

    return ready;
}

size_t RecurrentThread::getId() {
    return tid;
}

} // namespace thread
} // namespace uv
